/*
*  Copyright (c) 2023 Otávio Santana and others
*   All rights reserved. This program and the accompanying materials
*   are made available under the terms of the Eclipse Public License v1.0
*   and Apache License v2.0 which accompanies this distribution.
*   The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
*   and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
*
*   You may elect to redistribute this code under either of these licenses.
*
*   Contributors:
*
*   Otavio Santana
*/
package org.eclipse.jnosql.lite.mapping.metadata;


import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.jnosql.mapping.metadata.ClassInformationNotFoundException;
import org.eclipse.jnosql.mapping.metadata.EntitiesMetadata;
import org.eclipse.jnosql.mapping.metadata.EntityMetadata;
import org.eclipse.jnosql.mapping.metadata.InheritanceMetadata;
import org.eclipse.jnosql.mapping.metadata.ProjectionMetadata;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.annotation.processing.Generated;

@Generated(value= "JNoSQL Lite EntitiesMetadata Generator", date = "{{now}}")
@ApplicationScoped
public final class LiteEntitiesMetadata implements EntitiesMetadata {

    /**
    * The singleton instance
    */
    public static final LiteEntitiesMetadata INSTANCE = new LiteEntitiesMetadata();

    private static final Predicate<EntityMetadata> HAS_NAME = EntityMetadata::hasEntityName;

    private final List<LiteEntityMetadata> entities;
    private final Map<String, EntityMetadata> findByClassName;
    private final Map<String, EntityMetadata> findBySimpleName;
    private final Map<Class<?>, EntityMetadata> classes;
    private final Map<String, EntityMetadata> mappings;


    public LiteEntitiesMetadata() {
        this.findByClassName = new HashMap<>();
        this.findBySimpleName = new HashMap<>();
        this.classes = new HashMap<>();
        this.mappings = new HashMap<>();
        this.entities = new ArrayList<>();

        {{#entities}}
         this.entities.add(new {{.}}());
        {{/entities}}

        for (LiteEntityMetadata entity : entities) {
            this.classes.put(entity.type(), entity);
            if (entity.isEntity() && entity.hasEntityName()) {
                this.mappings.put(entity.name().toUpperCase(Locale.US), entity);
                this.findBySimpleName.put(entity.simpleName(), entity);
                this.findByClassName.put(entity.className(), entity);
            }
        }
    }


    @Override
    public EntityMetadata get(Class<?> entity) {
        Objects.requireNonNull(entity, "entity is required");
        EntityMetadata metadata = classes.get(entity);
        if (metadata == null) {
            throw new ClassInformationNotFoundException("The entity " + entity + " was not found");
        }
        return metadata;
    }

    @Override
    public Map<String, InheritanceMetadata> findByParentGroupByDiscriminatorValue(Class<?> parent) {
        Objects.requireNonNull(parent, "parent is required");
        return this.entities.stream()
                .flatMap(c -> c.inheritance().stream())
                .filter(p -> p.isParent(parent))
                .collect(Collectors.toMap(InheritanceMetadata::discriminatorValue, Function.identity()));
    }

    @Override
    public EntityMetadata findByName(String name) {
        Objects.requireNonNull(name, "name is required");
        return Optional.ofNullable(mappings.get(name.toUpperCase(Locale.US)))
                .orElseThrow(() -> new ClassInformationNotFoundException("There is not entity found with the name: " + name));

    }

    @Override
    public Optional<EntityMetadata> findBySimpleName(String name) {
        Objects.requireNonNull(name, "name is required");
        return Optional.ofNullable(findBySimpleName.get(name));
    }

    @Override
    public Optional<EntityMetadata> findByClassName(String name) {
        Objects.requireNonNull(name, "name is required");
        return Optional.ofNullable(findByClassName.get(name));
    }

    @Override
    public Optional<ProjectionMetadata> projection(Class<?> aClass) {
        return Optional.empty();
   }

    @Override
    public String toString() {
        return "DefaultEntitiesMetadata{" +
                "entities=" + entities +
                ", findByClassName=" + findByClassName +
                ", findBySimpleName=" + findBySimpleName +
                ", classes=" + classes +
                ", mappings=" + mappings +
                '}';
    }
}
